

<!DOCTYPE html>
<html lang="zh-CN" data-default-color-scheme=auto>



<head>
  <meta charset="UTF-8">
  <link rel="apple-touch-icon" sizes="76x76" href="/img/fluid.png">
  <link rel="icon" href="/img/fluid.png">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, shrink-to-fit=no">
  <meta http-equiv="x-ua-compatible" content="ie=edge">
  
  <meta name="theme-color" content="#2f4154">
  <meta name="author" content="John Doe">
  <meta name="keywords" content="">
  
    <meta name="description" content="@[toc] 一、项目介绍 此项目是我之前发布的 项目实战2: 基于SpringBoot的购物商城系统 的改良版，之前的项目定位是购物商城，但是缺了很多模块，比如没有评论、购物车、收藏等购物商城的基本功能，顶多只算是一个增删改查的案例，其中对于一些技术的应用只是表面层次，例如 Redis，仅仅用来保存有期限的验证码。 本次改良将项目定位成一套超市账单后台管理系统，该管理系统的主要特点是可以动态地修">
<meta property="og:type" content="article">
<meta property="og:title" content="项目实战4_ 基于 SpringBoot 的超市账单管理系统">
<meta property="og:url" content="http://example.com/2022/08/03/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%984_%20%E5%9F%BA%E4%BA%8E%20SpringBoot%20%E7%9A%84%E8%B6%85%E5%B8%82%E8%B4%A6%E5%8D%95%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/index.html">
<meta property="og:site_name" content="Hexo">
<meta property="og:description" content="@[toc] 一、项目介绍 此项目是我之前发布的 项目实战2: 基于SpringBoot的购物商城系统 的改良版，之前的项目定位是购物商城，但是缺了很多模块，比如没有评论、购物车、收藏等购物商城的基本功能，顶多只算是一个增删改查的案例，其中对于一些技术的应用只是表面层次，例如 Redis，仅仅用来保存有期限的验证码。 本次改良将项目定位成一套超市账单后台管理系统，该管理系统的主要特点是可以动态地修">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://img-blog.csdnimg.cn/d6c6a546cd434f70ba36e5535c8f8c03.png">
<meta property="og:image" content="https://img-blog.csdnimg.cn/a25cd0fb83f644ea8de39517bb09b30f.png">
<meta property="og:image" content="https://img-blog.csdnimg.cn/d520ea6cf812435bbb77451e72805371.png">
<meta property="og:image" content="https://img-blog.csdnimg.cn/4e9738104a16477282b34dc22915d410.png">
<meta property="og:image" content="https://img-blog.csdnimg.cn/3abf5d4c3fc24523907f586f1cbc7055.png">
<meta property="og:image" content="https://img-blog.csdnimg.cn/f5c5f488eb7746ad96ea21d8f0d75e98.png">
<meta property="og:image" content="https://img-blog.csdnimg.cn/0343a1a477f34ab49427a94fc75f7d7e.png">
<meta property="og:image" content="https://img-blog.csdnimg.cn/cda75b49217a4ed5b1eec90711208f3b.png">
<meta property="og:image" content="https://img-blog.csdnimg.cn/575938c79cd346859b3c6195bfe08917.png">
<meta property="og:image" content="https://img-blog.csdnimg.cn/e3b1fe95c5744b6b88045d3f67eb2a8c.png">
<meta property="og:image" content="https://img-blog.csdnimg.cn/59cf11470d7c49959d87e973b2311403.png">
<meta property="og:image" content="https://img-blog.csdnimg.cn/1027ba142d4e494184efb5d33351f058.png">
<meta property="og:image" content="https://img-blog.csdnimg.cn/6f1b97de557b4d4dbffc37ede3f6fa09.png">
<meta property="og:image" content="https://img-blog.csdnimg.cn/abef048037f44fe5998d4dfe648a0dd8.png">
<meta property="og:image" content="https://img-blog.csdnimg.cn/d099af8555bf4eb49a9d27038115e48f.png">
<meta property="og:image" content="https://img-blog.csdnimg.cn/2b40d8decfeb4cbfa4179f1953f5970b.png">
<meta property="og:image" content="https://img-blog.csdnimg.cn/5becabd640ba43b4b33735378caca092.png">
<meta property="og:image" content="https://img-blog.csdnimg.cn/63b8e22ec2c44ea481d7e78c621f02d3.png">
<meta property="og:image" content="https://img-blog.csdnimg.cn/a6d9d1b17ef847d0be8b526636212437.png">
<meta property="og:image" content="https://img-blog.csdnimg.cn/777d2959c87a4f9d8d69dca1b31a2239.png">
<meta property="og:image" content="https://img-blog.csdnimg.cn/42d2283f6f2546ba9b5fa21012c21c36.png">
<meta property="og:image" content="https://img-blog.csdnimg.cn/2f58bcae36e7495cbf8eab27aa4101f6.png">
<meta property="og:image" content="https://img-blog.csdnimg.cn/f98a071f20014761bdfb65c19c94b06d.png">
<meta property="og:image" content="https://img-blog.csdnimg.cn/f38aa2aa759d419795965d28cc6b3b89.png">
<meta property="og:image" content="https://img-blog.csdnimg.cn/538d34ec83e1402f9692de3666caf440.png">
<meta property="og:image" content="https://img-blog.csdnimg.cn/5f1e255c915c4761b89fdbb40040e570.png">
<meta property="og:image" content="https://img-blog.csdnimg.cn/9ef5d9da2d7e46e992505eefd072d34b.png">
<meta property="og:image" content="https://img-blog.csdnimg.cn/34ff26e283c14cdfbba71712fc4afd65.png">
<meta property="og:image" content="https://img-blog.csdnimg.cn/1c3c29837ece46adbde649d9627db75c.png">
<meta property="og:image" content="https://img-blog.csdnimg.cn/26f155121bdf4d36b252e26f09a12249.png">
<meta property="og:image" content="https://img-blog.csdnimg.cn/4387cc9c56bd46eaa007880c44f26fb1.png">
<meta property="og:image" content="https://img-blog.csdnimg.cn/f8d3b134a67a414f8542995ef00e6e1a.png">
<meta property="og:image" content="https://img-blog.csdnimg.cn/0d0b6a3e247a43b38135ae09aa43e9d1.png">
<meta property="og:image" content="https://img-blog.csdnimg.cn/358d3b5b953146489106cfec7c27a83d.png">
<meta property="article:published_time" content="2022-08-03T04:12:14.000Z">
<meta property="article:modified_time" content="2022-08-22T15:41:00.292Z">
<meta property="article:author" content="John Doe">
<meta property="article:tag" content="项目实战">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="https://img-blog.csdnimg.cn/d6c6a546cd434f70ba36e5535c8f8c03.png">
  
  
  
  <title>项目实战4_ 基于 SpringBoot 的超市账单管理系统 - Hexo</title>

  <link  rel="stylesheet" href="https://lib.baomitu.com/twitter-bootstrap/4.6.1/css/bootstrap.min.css" />



  <link  rel="stylesheet" href="https://lib.baomitu.com/github-markdown-css/4.0.0/github-markdown.min.css" />

  <link  rel="stylesheet" href="https://lib.baomitu.com/hint.css/2.7.0/hint.min.css" />

  <link  rel="stylesheet" href="https://lib.baomitu.com/fancybox/3.5.7/jquery.fancybox.min.css" />



<!-- 主题依赖的图标库，不要自行修改 -->
<!-- Do not modify the link that theme dependent icons -->

<link rel="stylesheet" href="//at.alicdn.com/t/font_1749284_hj8rtnfg7um.css">



<link rel="stylesheet" href="//at.alicdn.com/t/font_1736178_lbnruvf0jn.css">


<link  rel="stylesheet" href="/css/main.css" />


  <link id="highlight-css" rel="stylesheet" href="/css/highlight.css" />
  
    <link id="highlight-css-dark" rel="stylesheet" href="/css/highlight-dark.css" />
  




  <script id="fluid-configs">
    var Fluid = window.Fluid || {};
    Fluid.ctx = Object.assign({}, Fluid.ctx)
    var CONFIG = {"hostname":"example.com","root":"/","version":"1.9.2","typing":{"enable":true,"typeSpeed":70,"cursorChar":"_","loop":false,"scope":[]},"anchorjs":{"enable":true,"element":"h1,h2,h3,h4,h5,h6","placement":"left","visible":"hover","icon":""},"progressbar":{"enable":true,"height_px":3,"color":"#29d","options":{"showSpinner":false,"trickleSpeed":100}},"code_language":{"enable":true,"default":"TEXT"},"copy_btn":true,"image_caption":{"enable":true},"image_zoom":{"enable":true,"img_url_replace":["",""]},"toc":{"enable":true,"placement":"right","headingSelector":"h1,h2,h3,h4,h5,h6","collapseDepth":0},"lazyload":{"enable":true,"loading_img":"/img/loading.gif","onlypost":false,"offset_factor":2},"web_analytics":{"enable":false,"follow_dnt":true,"baidu":null,"google":null,"gtag":null,"tencent":{"sid":null,"cid":null},"woyaola":null,"cnzz":null,"leancloud":{"app_id":null,"app_key":null,"server_url":null,"path":"window.location.pathname","ignore_local":false}},"search_path":"/local-search.xml"};

    if (CONFIG.web_analytics.follow_dnt) {
      var dntVal = navigator.doNotTrack || window.doNotTrack || navigator.msDoNotTrack;
      Fluid.ctx.dnt = dntVal && (dntVal.startsWith('1') || dntVal.startsWith('yes') || dntVal.startsWith('on'));
    }
  </script>
  <script  src="/js/utils.js" ></script>
  <script  src="/js/color-schema.js" ></script>
  


  
<meta name="generator" content="Hexo 6.2.0"></head>


<body>
  

  <header>
    

<div class="header-inner" style="height: 70vh;">
  <nav id="navbar" class="navbar fixed-top  navbar-expand-lg navbar-dark scrolling-navbar">
  <div class="container">
    <a class="navbar-brand" href="/">
      <strong>尤 Ni&#39;s Blog</strong>
    </a>

    <button id="navbar-toggler-btn" class="navbar-toggler" type="button" data-toggle="collapse"
            data-target="#navbarSupportedContent"
            aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <div class="animated-icon"><span></span><span></span><span></span></div>
    </button>

    <!-- Collapsible content -->
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav ml-auto text-center">
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/">
                <i class="iconfont icon-home-fill"></i>
                首页
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/archives/">
                <i class="iconfont icon-archive-fill"></i>
                归档
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/categories/">
                <i class="iconfont icon-category-fill"></i>
                分类
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/tags/">
                <i class="iconfont icon-tags-fill"></i>
                标签
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/about/">
                <i class="iconfont icon-user-fill"></i>
                关于
              </a>
            </li>
          
        
        
          <li class="nav-item" id="search-btn">
            <a class="nav-link" target="_self" href="javascript:;" data-toggle="modal" data-target="#modalSearch" aria-label="Search">
              &nbsp;<i class="iconfont icon-search"></i>&nbsp;
            </a>
          </li>
          
        
        
          <li class="nav-item" id="color-toggle-btn">
            <a class="nav-link" target="_self" href="javascript:;" aria-label="Color Toggle">&nbsp;<i
                class="iconfont icon-dark" id="color-toggle-icon"></i>&nbsp;</a>
          </li>
        
      </ul>
    </div>
  </div>
</nav>

  

<div id="banner" class="banner" parallax=true
     style="background: url('/img/default.png') no-repeat center center; background-size: cover;">
  <div class="full-bg-img">
    <div class="mask flex-center" style="background-color: rgba(0, 0, 0, 0.3)">
      <div class="banner-text text-center fade-in-up">
        <div class="h2">
          
            <span id="subtitle" data-typed-text="项目实战4_ 基于 SpringBoot 的超市账单管理系统"></span>
          
        </div>

        
          
  <div class="mt-3">
    
      <span class="post-meta mr-2">
        <i class="iconfont icon-author" aria-hidden="true"></i>
        John Doe
      </span>
    
    
      <span class="post-meta">
        <i class="iconfont icon-date-fill" aria-hidden="true"></i>
        <time datetime="2022-08-03 12:12" pubdate>
          2022年8月3日 中午
        </time>
      </span>
    
  </div>

  <div class="mt-1">
    
      <span class="post-meta mr-2">
        <i class="iconfont icon-chart"></i>
        
          10k 字
        
      </span>
    

    
      <span class="post-meta mr-2">
        <i class="iconfont icon-clock-fill"></i>
        
        
        
          85 分钟
        
      </span>
    

    
    
  </div>


        
      </div>

      
    </div>
  </div>
</div>

</div>

  </header>

  <main>
    
      

<div class="container-fluid nopadding-x">
  <div class="row nomargin-x">
    <div class="side-col d-none d-lg-block col-lg-2">
      

    </div>

    <div class="col-lg-8 nopadding-x-md">
      <div class="container nopadding-x-md" id="board-ctn">
        <div id="board">
          <article class="post-content mx-auto">
            <!-- SEO header -->
            <h1 style="display: none">项目实战4_ 基于 SpringBoot 的超市账单管理系统</h1>
            
              <p class="note note-info">
                
                  
                    本文最后更新于：1 小时前
                  
                
              </p>
            
            
              <div class="markdown-body">
                
                <p>@[toc]</p>
<h1 id="一、项目介绍"><a href="#一、项目介绍" class="headerlink" title="一、项目介绍"></a>一、项目介绍</h1><hr>
<p>此项目是我之前发布的 <a target="_blank" rel="noopener" href="https://blog.csdn.net/unirithe/category_11875707.html">项目实战2: 基于SpringBoot的购物商城系统</a> 的改良版，之前的项目定位是购物商城，但是缺了很多模块，比如没有评论、购物车、收藏等购物商城的基本功能，顶多只算是一个增删改查的案例，其中对于一些技术的应用只是表面层次，例如 Redis，仅仅用来保存有期限的验证码。</p>
<p>本次改良将项目定位成一套超市账单后台管理系统，该管理系统的主要特点是可以动态地修改不同角色的权限。结合现实生活中，超市里不同身份的人具有不同的职责与权限。这次的项目更有专一性，虽然仍然缺少许多内容，但是可以作为 SpringBoot 增删改查的练手项目。</p>
<p>目前这个项目存在的不足（部分）：</p>
<ul>
<li>缺少日志管理</li>
<li>缺少导入、导出</li>
<li>缺少系统备份</li>
<li>存在一些业务逻辑的 BUG，但不影响基本使用</li>
<li>…</li>
</ul>
<p>采用技术栈：</p>
<p>![在这里插入图片描述](<a target="_blank" rel="noopener" href="https://img-blog.csdnimg.cn/ae617a185e4c4c9391143379b99d79b3.png#pic_center">https://img-blog.csdnimg.cn/ae617a185e4c4c9391143379b99d79b3.png#pic_center</a> &#x3D;80%x)</p>
<p>用到的技术栈包括工具：<br>![在这里插入图片描述](<a target="_blank" rel="noopener" href="https://img-blog.csdnimg.cn/976296670f7f4f6286325735222371dd.png#pic_center">https://img-blog.csdnimg.cn/976296670f7f4f6286325735222371dd.png#pic_center</a> &#x3D;80%x)</p>
<h1 id="二、数据库设计"><a href="#二、数据库设计" class="headerlink" title="二、数据库设计"></a>二、数据库设计</h1><hr>
<p>![在这里插入图片描述](<a target="_blank" rel="noopener" href="https://img-blog.csdnimg.cn/612c2393b0794487a3844ac80d0ae907.jpeg#pic_center">https://img-blog.csdnimg.cn/612c2393b0794487a3844ac80d0ae907.jpeg#pic_center</a> &#x3D;80%x)</p>
<h1 id="三、概要设计"><a href="#三、概要设计" class="headerlink" title="三、概要设计"></a>三、概要设计</h1><hr>
<h2 id="确立对象，划分模块"><a href="#确立对象，划分模块" class="headerlink" title="确立对象，划分模块"></a>确立对象，划分模块</h2><p>首先确定系统的对象，然后分为不同的模块，根据对象的不同场景继续划分模块。</p>
<p>以最基本的系统用户出发，目前我设计的用户对象有以下 5 种：</p>
<p><strong>1. 供应商管理员</strong></p>
<p>用于管理超市中供应商的角色，主要是对于供应商信息进行管理，比如查询、添加、删除、修改供应商。</p>
<p><strong>2. 销售员</strong></p>
<p>根据商品来生成账单的角色，类似超市的收银员，比如查询商品、生成账单，修改账单状态。</p>
<p><strong>3. 商品管理员</strong></p>
<p>主要管理商品的角色，类似超市的货物管理员，负责管理每个商品的信息（位置、价格等），我们这里不涉及位置的管理，比如查询商品、修改商品信息、删除商品。</p>
<p><strong>4. 网站管理员</strong></p>
<p>具有网站所有权限的角色，是管理其他所有角色的关键角色，比如管理用户信息、用户角色、角色权限等。</p>
<p><strong>5. 经理</strong></p>
<p>查询系统的营收情况，后台统计报表信息，不用编辑任何信息，只需要查看可视化的图表，同时还可以查看用户信息，用户角色等。</p>
<p>综上，以上 5 种角色是结合之前的系统设计出来的，之前的项目确实太乱了，定位不清晰，现在这么简单的列出五种角色后，就清楚了许多，而且他们各司其职，我们可以更好的利用 Shrio 框架来进行权限管理。</p>
<h1 id="四、详细设计"><a href="#四、详细设计" class="headerlink" title="四、详细设计"></a>四、详细设计</h1><hr>
<p>详细设计这一块不知道如何描述，我在写这个 “项目” 的时候，基本上就是想到什么写什么，只知道大概的流程，比如先从注册、登录的页面开始写起，写完页面，然后再写 Controller、Service、DAO。</p>
<p>现在发现，之前真的浪费太多时间了，页面能复制的就直接复制了，需要改的就改一下，目前接触了  Vue2，发现前端工程化的编码加上 组件库，非常容易上手而且写出来很清晰，像我在这个项目写的，尽管文件区分比较明确，但是里面的逻辑依然比较繁琐。</p>
<p>这里不一 一描述我这拙劣的设计方法了（太一般啦），就挑几个我自认为需要记录的内容说一下吧。</p>
<h2 id="4-1-Shiro-授权与鉴权流程梳理"><a href="#4-1-Shiro-授权与鉴权流程梳理" class="headerlink" title="4.1 Shiro 授权与鉴权流程梳理"></a>4.1 Shiro 授权与鉴权流程梳理</h2><h3 id="4-1-1-了解-Shiro"><a href="#4-1-1-了解-Shiro" class="headerlink" title="4.1.1 了解 Shiro"></a>4.1.1 了解 Shiro</h3><p>参考资料：<a target="_blank" rel="noopener" href="https://www.w3cschool.cn/shiro/co4m1if2.html">Shiro 教程</a></p>
<p>Apache Shiro 是 Java 的一个安全框架。对比 Spring Security，功能没有那么强大，但是比较灵活。</p>
<p>既支持 普通的 Java 程序，又支持 Web开发的 JavaEE应用程序。</p>
<p>Shiro 解决的问题：<br>![在这里插入图片描述](<a target="_blank" rel="noopener" href="https://img-blog.csdnimg.cn/82b37bcb48cf401fad4e991333985237.png#pic_center">https://img-blog.csdnimg.cn/82b37bcb48cf401fad4e991333985237.png#pic_center</a> &#x3D;80%x)<br>Shiro 架构图：<br>![在这里插入图片描述](<a target="_blank" rel="noopener" href="https://img-blog.csdnimg.cn/92663da02af14973af985f2d1e372440.png#pic_center">https://img-blog.csdnimg.cn/92663da02af14973af985f2d1e372440.png#pic_center</a> &#x3D;90%x)</p>
<ul>
<li><p>Subject：主体，代表了当前 “用户”，属于抽象概念，所有 Subject 都会绑定到 SecurityManager。</p>
</li>
<li><p>SecurityManager：安全管理器，管理所有的 Subject；是 Shiro 的核心，类似 SpringMVC 的 DispatcherServlet 。</p>
</li>
<li><p>Realm：域，Shiro 从 Realm 获取安全数据（如用户、角色、权限），可以把 Realm 看成 DataSource，即数据源。</p>
</li>
</ul>
<p>当用户登录时需进行授权，则需提前指定权限，然后再给指定的用户赋予相应的权限， Shiro 支持使用 ini 配置文件来授权，同样支持 DB 数据库来授权，这里则使用 MySQL 来存储 权限的内容，之后通过硬编码的方式，将 MySQL的权限注册到 Shiro 的会话中心。</p>
<h3 id="4-1-2-Shiro-数据库设计"><a href="#4-1-2-Shiro-数据库设计" class="headerlink" title="4.1.2 Shiro 数据库设计"></a>4.1.2 Shiro 数据库设计</h3><p>参考：<a target="_blank" rel="noopener" href="https://www.cnblogs.com/niuli1987/p/9871182.html">RBAC模型 资料</a></p>
<p>Shiro 是用于授权和鉴权的框架，除此之外还需要使用 RBAC 模型来形成完整的一套支持权限管理的系统。 RBAC 即 Role-Base-Access-Controle ，基于角色的访问控制。</p>
<p>RBAC 模型分为四种，分别是 RBAC0、RBAC1、RBAC2 和 RBAC3， 安全等级依次提升。</p>
<p>目前了解 RBAC0 就行，只有更复杂的业务系统才需要更高权限模型。</p>
<p>RBAC0 是其他三个模型的基础，提出的是用户、角色、权限之间的关系，如下图：</p>
<p>![在这里插入图片描述](<a target="_blank" rel="noopener" href="https://img-blog.csdnimg.cn/9a987a495d1045ecae3c31b9911ee16f.png#pic_center">https://img-blog.csdnimg.cn/9a987a495d1045ecae3c31b9911ee16f.png#pic_center</a> &#x3D;80%x)</p>
<p>根据这个模型来建立 五张数据表，如下图：</p>
<p><img src="https://img-blog.csdnimg.cn/d6c6a546cd434f70ba36e5535c8f8c03.png" srcset="/img/loading.gif" lazyload alt="在这里插入图片描述"></p>
<h3 id="4-1-3-SpringBoot-整合-Shiro-思路"><a href="#4-1-3-SpringBoot-整合-Shiro-思路" class="headerlink" title="4.1.3 SpringBoot  整合 Shiro 思路"></a>4.1.3 SpringBoot  整合 Shiro 思路</h3><h4 id="1-引入-Shiro-依赖"><a href="#1-引入-Shiro-依赖" class="headerlink" title="1. 引入 Shiro 依赖"></a>1. 引入 Shiro 依赖</h4><p>这里使用 Spring 整合后的 Shiro 依赖 <code>shiro-spring</code> ，当引入依赖后经过一些配置，将 Shiro 相关的组件注入到 Spring 的容器中。</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.apache.shiro<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>shiro-spring<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>1.6.0<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br></code></pre></td></tr></table></figure>

<h4 id="2-自定义-Realm"><a href="#2-自定义-Realm" class="headerlink" title="2. 自定义 Realm"></a>2. 自定义 Realm</h4><p>Shiro 的 Realm 层负责和数据库交互，定义认证与授权这两个过程，接下来是 Java 代码实现的流程：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.star.ms.admin.shiro;<br><br><span class="hljs-keyword">import</span> org.apache.shiro.realm.AuthorizingRealm;<br><span class="hljs-comment">// 实现 Realm 需要继承 Shiro 提供的 AuthorizingRealm 类 </span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">CustomRealm</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">AuthorizingRealm</span> &#123;<br>	<span class="hljs-comment">// 从 Spring 容器获取 业务逻辑层中 用户、角色、权限的实现</span><br>    <span class="hljs-meta">@Autowired</span><br>    <span class="hljs-keyword">private</span> UserService userService;<br>    <span class="hljs-meta">@Autowired</span><br>    <span class="hljs-keyword">private</span> RoleService roleService;<br>    <span class="hljs-meta">@Autowired</span><br>    <span class="hljs-keyword">private</span> PermService permService;<br>    <span class="hljs-comment">// 授权</span><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">protected</span> AuthorizationInfo <span class="hljs-title function_">doGetAuthorizationInfo</span><span class="hljs-params">(PrincipalCollection principalCollection)</span> &#123;<br>        <span class="hljs-type">SimpleAuthorizationInfo</span> <span class="hljs-variable">info</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">SimpleAuthorizationInfo</span>();<br>        <span class="hljs-type">String</span> <span class="hljs-variable">principal</span> <span class="hljs-operator">=</span> (String) principalCollection.getPrimaryPrincipal();<br>        <span class="hljs-comment">// 获取当前的用户信息</span><br>        <span class="hljs-type">User</span> <span class="hljs-variable">user</span> <span class="hljs-operator">=</span> userService.getByCode(principal);<br>        <span class="hljs-keyword">if</span>(user != <span class="hljs-literal">null</span>)&#123;<br>            <span class="hljs-comment">// 获取用户的角色</span><br>            <span class="hljs-type">Role</span> <span class="hljs-variable">role</span> <span class="hljs-operator">=</span> roleService.getByUserCode(user.getUsercode());<br>            user.setRole(role);<br>            <span class="hljs-comment">// 赋予当前用户对应的角色信息（相同角色的权限是一致的）</span><br>            <span class="hljs-keyword">if</span>(role != <span class="hljs-literal">null</span>) &#123;<br>                info.addRole(role.getName());<br><span class="hljs-comment">//                 添加权限</span><br>                List&lt;Perm&gt; perms = permService.getByRoleId(role.getId());<br>                <span class="hljs-keyword">if</span>(perms!=<span class="hljs-literal">null</span> &amp;&amp; !CollectionUtils.isEmpty(perms)) &#123;<br>                    perms.forEach(perm -&gt; &#123;<br>                        info.addStringPermission(perm.getUrl());<br>                    &#125;);<br>                &#125;<br>            &#125;<br>        &#125;<br>        <span class="hljs-keyword">return</span> info;<br>    &#125;<br><br>    <span class="hljs-comment">// 认证</span><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">protected</span> AuthenticationInfo <span class="hljs-title function_">doGetAuthenticationInfo</span><span class="hljs-params">(AuthenticationToken token)</span><br>            <span class="hljs-keyword">throws</span> AuthenticationException &#123;<br>        <span class="hljs-comment">// 获取身份信息</span><br>        <span class="hljs-type">String</span> <span class="hljs-variable">principal</span> <span class="hljs-operator">=</span> (String) token.getPrincipal();<br>        <span class="hljs-comment">// 调用业务层，获取数据库里对应的用户信息</span><br>        <span class="hljs-type">User</span> <span class="hljs-variable">user</span> <span class="hljs-operator">=</span> userService.getByCode(principal);<br>        <span class="hljs-keyword">if</span>(!ObjectUtils.isEmpty(user))&#123;<br>            <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">SimpleAuthenticationInfo</span>(<br>                    principal,<br>                    user.getPassword(),<br>                    ByteSource.Util.bytes(user.getSalt()),<br>                    <span class="hljs-built_in">this</span>.getName());<br>        &#125;<br>        <span class="hljs-keyword">else</span> <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;<br>    &#125;<br>&#125;<br><br></code></pre></td></tr></table></figure>
<p>自定义 Realm 类里制定了 用户在注册登录时候的认证过程 doGetAuthenticationInfo，以及在登录后的授权过程 doGetAuthorizationInfo。</p>
<h4 id="3-注入-Shiro-的组件到-IOC-容器中"><a href="#3-注入-Shiro-的组件到-IOC-容器中" class="headerlink" title="3. 注入 Shiro 的组件到 IOC 容器中"></a>3. 注入 Shiro 的组件到 IOC 容器中</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.star.ms.config;<br><br><span class="hljs-meta">@Configuration</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">ShiroConfig</span> &#123;<br><br>    <span class="hljs-meta">@Autowired</span><br>    RoleService roleService;<br>    <span class="hljs-meta">@Autowired</span><br>    PermService permService;<br>    <span class="hljs-comment">// 创建 Realm 对象</span><br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-keyword">public</span> CustomRealm <span class="hljs-title function_">customerRealm</span><span class="hljs-params">()</span>&#123;<br>        <span class="hljs-type">CustomRealm</span> <span class="hljs-variable">realm</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">CustomRealm</span>();<br>        <span class="hljs-comment">// 修改凭证校验匹配器</span><br>        <span class="hljs-type">HashedCredentialsMatcher</span> <span class="hljs-variable">hcm</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">HashedCredentialsMatcher</span>();<br>        hcm.setStoredCredentialsHexEncoded(<span class="hljs-literal">true</span>);<br>        <span class="hljs-comment">// 设置加密算法为 MD5</span><br>        hcm.setHashAlgorithmName(<span class="hljs-string">&quot;MD5&quot;</span>);<br>        <span class="hljs-comment">// 设置散列次数</span><br>        hcm.setHashIterations(<span class="hljs-number">1024</span>);<br>        realm.setCredentialsMatcher(hcm);<br>        <span class="hljs-keyword">return</span> realm;<br>    &#125;<br>    <span class="hljs-comment">// 安全管理器</span><br>    <span class="hljs-meta">@Bean(name=&quot;securityManager&quot;)</span><br>    <span class="hljs-keyword">public</span> DefaultWebSecurityManager <span class="hljs-title function_">getDefaultWebSecurityManager</span><span class="hljs-params">(</span><br><span class="hljs-params">            <span class="hljs-meta">@Qualifier(&quot;customerRealm&quot;)</span> Realm realm)</span>&#123;<br>        <span class="hljs-type">DefaultWebSecurityManager</span> <span class="hljs-variable">securityManager</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">DefaultWebSecurityManager</span>();<br>        securityManager.setRealm(realm);<br>        <span class="hljs-keyword">return</span> securityManager;<br>    &#125;<br>    <span class="hljs-comment">// 过滤链工厂</span><br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-keyword">public</span> ShiroFilterFactoryBean <span class="hljs-title function_">getShiroFilterFactoryBean</span><span class="hljs-params">(</span><br><span class="hljs-params">            <span class="hljs-meta">@Qualifier(&quot;securityManager&quot;)</span> DefaultWebSecurityManager defaultWebSecurityManager)</span>&#123;<br>        <span class="hljs-type">ShiroFilterFactoryBean</span> <span class="hljs-variable">bean</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ShiroFilterFactoryBean</span>();<br>        <span class="hljs-comment">// 设置安全管理器</span><br>        bean.setSecurityManager(defaultWebSecurityManager);<br>        Map&lt;String, String&gt; filterMap = <span class="hljs-keyword">new</span> <span class="hljs-title class_">LinkedHashMap</span>&lt;&gt;();<br><br>        <span class="hljs-comment">// 获取所有权限信息 配置数据库的权限信息</span><br>        List&lt;Perm&gt; perms = permService.list();<br>        perms.forEach(perm -&gt;&#123;<br>            <span class="hljs-keyword">for</span> (String p : perm.getUrl().split(<span class="hljs-string">&quot;,&quot;</span>)) &#123;<br>                filterMap.put(p, String.format(<span class="hljs-string">&quot;perms[%s]&quot;</span>,p));<br>            &#125;<br>        &#125;);<br>        <span class="hljs-comment">// 配置公共权限 (游客的权限)</span><br>        filterMap.put(<span class="hljs-string">&quot;/user/login&quot;</span>, <span class="hljs-string">&quot;anon&quot;</span>);<br>        filterMap.put(<span class="hljs-string">&quot;/user/verify&quot;</span>, <span class="hljs-string">&quot;anon&quot;</span>);<br>        filterMap.put(<span class="hljs-string">&quot;/register/*&quot;</span>, <span class="hljs-string">&quot;anon&quot;</span>);<br>        filterMap.put(<span class="hljs-string">&quot;/login.html&quot;</span>, <span class="hljs-string">&quot;anon&quot;</span>);<br>        filterMap.put(<span class="hljs-string">&quot;/home.html&quot;</span>, <span class="hljs-string">&quot;anon&quot;</span>);<br>        bean.setFilterChainDefinitionMap(filterMap);<br>        <span class="hljs-comment">// 设置登录的请求</span><br>        bean.setLoginUrl(<span class="hljs-string">&quot;/login.html&quot;</span>);<br>        <span class="hljs-comment">// 未授权页面</span><br>        bean.setUnauthorizedUrl(<span class="hljs-string">&quot;/noauth&quot;</span>);<br>        <span class="hljs-keyword">return</span> bean;<br>    &#125;<br>    <span class="hljs-comment">// 整合 ShiroDialect: 用来整合 shiro thymeleaf</span><br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-keyword">public</span> ShiroDialect <span class="hljs-title function_">getShiroDialect</span><span class="hljs-params">()</span>&#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ShiroDialect</span>();<br>    &#125;<br>    <span class="hljs-comment">// Shiro注解开发需要的Bean</span><br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-keyword">public</span> AuthorizationAttributeSourceAdvisor <span class="hljs-title function_">getAuthorizationAttributeSourceAdvisor</span><span class="hljs-params">()</span>&#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">AuthorizationAttributeSourceAdvisor</span>();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<p>注入的 Bean 主要有 1）自定义的 Realm 对象；2）Shiro 安全管理器 ；3）Shiro 的 过滤链工厂 。</p>
<ul>
<li>自定义 Realm，负责指定 Shiro 进行认证和鉴权的一种策略。</li>
<li>Shiro 安全管理器，负责添加自定义的 Realm，在用户登录前后走自定义的认证与授权过程。</li>
<li>Shiro 过滤链工厂，负责指定有哪些权限的限制，包括设置公用的资源。</li>
</ul>
<h4 id="4-Shiro-的使用"><a href="#4-Shiro-的使用" class="headerlink" title="4. Shiro 的使用"></a>4. Shiro 的使用</h4><p>在配置好 Shrio 的权限以及用户的认证和鉴权方式后，接下来就是关于 Shiro 的使用，以 UserService 中的登录为例</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> User <span class="hljs-title function_">login</span><span class="hljs-params">(User user)</span> &#123;<br>   <span class="hljs-type">User</span> <span class="hljs-variable">loginUser</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>;<br>   <span class="hljs-keyword">if</span>(user.getUsercode()!=<span class="hljs-literal">null</span> &amp;&amp; user.getPassword()!=<span class="hljs-literal">null</span>) &#123;<br>       loginUser = userDAO.selectByCode(user.getUsercode());<br>       <span class="hljs-comment">// 封装一个 Token</span><br>       <span class="hljs-type">UsernamePasswordToken</span> <span class="hljs-variable">token</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">UsernamePasswordToken</span>(user.getUsercode(), user.getPassword(), <span class="hljs-literal">false</span>);<br>       <span class="hljs-comment">// 获取主体</span><br>       <span class="hljs-type">Subject</span> <span class="hljs-variable">subject</span> <span class="hljs-operator">=</span> SecurityUtils.getSubject();<br>       subject.login(token);<br>       <span class="hljs-comment">// 验证成功后返回对象</span><br>       <span class="hljs-comment">// 查询角色</span><br>       loginUser.setRole(roleService.getByUserCode(loginUser.getUsercode()));<br>   &#125;<br>   <span class="hljs-keyword">return</span> loginUser;<br>&#125;<br></code></pre></td></tr></table></figure>
<p>认证使用流程如下：</p>
<ol>
<li>通过 SecurityUtils 对象获取到当前的 Subject </li>
<li>根据用户的账号和密码封装一个 Shiro 提供的 Token 类</li>
<li>调用 Subject 的 login方法，将 token 传入自定义Realm的认证方法，如果 login 登录失败的话就会抛出 Shiro 提供的几个异常，这里我没有捕获异常，就默认不会出现。</li>
</ol>
<p>Shiro 鉴权的使用有许多，比如在 Controller 层：同样是通过 SecurityUtils 获取 Subject，然后调用 Subject 对象的 isPermmited 判断是否有某个权限。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@GetMapping(&quot;/admin/&#123;type&#125;&quot;)</span><br><span class="hljs-keyword">public</span> String <span class="hljs-title function_">toAdminUser</span><span class="hljs-params">(Model model, <span class="hljs-meta">@PathVariable</span> String type)</span>&#123;<br>        &lt;dependency&gt;<br>            &lt;groupId&gt;com.github.theborakompanioni&lt;/groupId&gt;<br>            &lt;artifactId&gt;thymeleaf-extras-shiro&lt;/artifactId&gt;<br>            &lt;version&gt;<span class="hljs-number">2.1</span><span class="hljs-number">.0</span>&lt;/version&gt;<br>        &lt;/dependency&gt;<br></code></pre></td></tr></table></figure>
<p>另一种就是使用经过 Thymeleaf 模板引擎渲染的 HTML，需要单独引入 thymeleaf-shiro的依赖</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.github.theborakompanioni<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>thymeleaf-extras-shiro<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>2.1.0<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br></code></pre></td></tr></table></figure>


<p>使用时：</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">xmlns:shiro</span>=<span class="hljs-string">&quot;http://www.pollix.at/thymeleaf/shiro&quot;</span>&gt;</span><br> 	shiro:hasRole=&quot;管理员&quot;&gt;<span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span><br></code></pre></td></tr></table></figure>

<p>至此， Shiro 的详细设计已完毕。</p>
<h2 id="4-2-阿里-OSS-云存储服务"><a href="#4-2-阿里-OSS-云存储服务" class="headerlink" title="4.2 阿里 OSS 云存储服务"></a>4.2 阿里 OSS 云存储服务</h2><p>为了减轻服务器压力，对于管理系统的图片，我一致使用的是 阿里云的 OSS 云存储服务，首先得费用很低，一年10块左右，其次是更安全、更高效。</p>
<p>阿里云 OSS 官方地址：<a target="_blank" rel="noopener" href="https://oss.console.aliyun.com/bucket">阿里云 OSS 服务</a></p>
<p>SpringBoot 整合 阿里云 OSS 可参考这篇文章：<a target="_blank" rel="noopener" href="https://www.jianshu.com/p/978e206ab8fd">SpringBoot 整合 OSS</a></p>
<p>项目中用 OSS 的方法是先声明 OssService 接口，定义一些与OSS交互有关的方法。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.star.ms.common.service;<br><br><br><span class="hljs-keyword">import</span> com.star.ms.common.entity.api.OssUploadResult;<br><span class="hljs-keyword">import</span> org.springframework.stereotype.Service;<br><br><span class="hljs-keyword">import</span> java.util.List;<br><br><span class="hljs-meta">@Service</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">OssService</span> &#123;<br>    <span class="hljs-comment">// 获取所有图片的路径</span><br>    <span class="hljs-keyword">public</span> List&lt;String[]&gt; getDefaultHeadImgLink();<br>    <span class="hljs-comment">// 上传文件</span><br>    <span class="hljs-keyword">public</span> OssUploadResult <span class="hljs-title function_">upload</span><span class="hljs-params">(String userCode, String img64base)</span>;<br>    <span class="hljs-comment">// 获取文件路径</span><br>    <span class="hljs-keyword">public</span> String <span class="hljs-title function_">getUserImgPath</span><span class="hljs-params">(String userCode, String imgType)</span>;<br><br>&#125;<br></code></pre></td></tr></table></figure>

<p>这些方法会用到 OSS 依赖提供的 OSS 对象，所以在实现这个接口前，需要声明 OSS 相关的 Bean 到 Spring 容器</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.star.ms.common.api;<br><br><span class="hljs-keyword">import</span> com.aliyun.oss.*;<br><span class="hljs-keyword">import</span> com.aliyun.oss.model.ListObjectsRequest;<br><span class="hljs-keyword">import</span> com.aliyun.oss.model.OSSObjectSummary;<br><span class="hljs-keyword">import</span> com.aliyun.oss.model.ObjectListing;<br><span class="hljs-keyword">import</span> lombok.Data;<br><span class="hljs-keyword">import</span> org.springframework.beans.factory.annotation.Value;<br><span class="hljs-keyword">import</span> org.springframework.context.annotation.Bean;<br><span class="hljs-keyword">import</span> org.springframework.context.annotation.Configuration;<br><span class="hljs-keyword">import</span> org.springframework.stereotype.Component;<br><br><span class="hljs-keyword">import</span> java.util.LinkedList;<br><span class="hljs-keyword">import</span> java.util.List;<br><br><span class="hljs-meta">@Configuration</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">OssApi</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">String</span> <span class="hljs-variable">ENDPOINT</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;OSS的域&quot;</span>;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">String</span> <span class="hljs-variable">ACCESS_KEY_ID</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;OSS的验证ID&quot;</span>;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">String</span> <span class="hljs-variable">ACCESS_KEY_SECRET</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;OSS的验证密钥&quot;</span>;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">String</span> <span class="hljs-variable">BUCKET_NAME</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;OSS中Buncket块的名称&quot;</span>;<br><br>	<span class="hljs-comment">// 根据配置将 OSS 注入到 Spring 的 IoC 容器</span><br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-keyword">public</span> OSS <span class="hljs-title function_">OSSClient</span><span class="hljs-params">()</span>&#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">OSSClientBuilder</span>().build(ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<h2 id="4-3-页面设计"><a href="#4-3-页面设计" class="headerlink" title="4.3 页面设计"></a>4.3 页面设计</h2><p>以下页面是我用 Bootstrap5 凑出来的，可能不太没美观，以简约为主。由于想着用 AJAX 请求数据，尽量做到前后端分离，结果在后面用到了 Boostrap5 的模态框，写了很多重复的杂冗的 JS 代码…</p>
<h3 id="1-登录页面"><a href="#1-登录页面" class="headerlink" title="1. 登录页面"></a>1. 登录页面</h3><p><img src="https://img-blog.csdnimg.cn/a25cd0fb83f644ea8de39517bb09b30f.png" srcset="/img/loading.gif" lazyload alt="在这里插入图片描述"></p>
<h3 id="2-注册页面"><a href="#2-注册页面" class="headerlink" title="2. 注册页面"></a>2. 注册页面</h3><p><img src="https://img-blog.csdnimg.cn/d520ea6cf812435bbb77451e72805371.png" srcset="/img/loading.gif" lazyload alt="在这里插入图片描述"></p>
<h3 id="3-后台报表页面"><a href="#3-后台报表页面" class="headerlink" title="3. 后台报表页面"></a>3. 后台报表页面</h3><p><img src="https://img-blog.csdnimg.cn/4e9738104a16477282b34dc22915d410.png" srcset="/img/loading.gif" lazyload alt="在这里插入图片描述"></p>
<h3 id="4-后台用户管理页面"><a href="#4-后台用户管理页面" class="headerlink" title="4. 后台用户管理页面"></a>4. 后台用户管理页面</h3><p><img src="https://img-blog.csdnimg.cn/3abf5d4c3fc24523907f586f1cbc7055.png" srcset="/img/loading.gif" lazyload alt="在这里插入图片描述"></p>
<p><img src="https://img-blog.csdnimg.cn/f5c5f488eb7746ad96ea21d8f0d75e98.png" srcset="/img/loading.gif" lazyload alt="在这里插入图片描述"></p>
<p><img src="https://img-blog.csdnimg.cn/0343a1a477f34ab49427a94fc75f7d7e.png" srcset="/img/loading.gif" lazyload alt="在这里插入图片描述"></p>
<h3 id="5-用户角色管理页面"><a href="#5-用户角色管理页面" class="headerlink" title="5. 用户角色管理页面"></a>5. 用户角色管理页面</h3><p><img src="https://img-blog.csdnimg.cn/cda75b49217a4ed5b1eec90711208f3b.png" srcset="/img/loading.gif" lazyload alt="在这里插入图片描述"></p>
<p><img src="https://img-blog.csdnimg.cn/575938c79cd346859b3c6195bfe08917.png" srcset="/img/loading.gif" lazyload alt="在这里插入图片描述"></p>
<p><img src="https://img-blog.csdnimg.cn/e3b1fe95c5744b6b88045d3f67eb2a8c.png" srcset="/img/loading.gif" lazyload alt="在这里插入图片描述"></p>
<h3 id="6-供应商管理页面"><a href="#6-供应商管理页面" class="headerlink" title="6. 供应商管理页面"></a>6. 供应商管理页面</h3><p><img src="https://img-blog.csdnimg.cn/59cf11470d7c49959d87e973b2311403.png" srcset="/img/loading.gif" lazyload alt="在这里插入图片描述"></p>
<p><img src="https://img-blog.csdnimg.cn/1027ba142d4e494184efb5d33351f058.png" srcset="/img/loading.gif" lazyload alt="在这里插入图片描述"></p>
<p><img src="https://img-blog.csdnimg.cn/6f1b97de557b4d4dbffc37ede3f6fa09.png" srcset="/img/loading.gif" lazyload alt="在这里插入图片描述"></p>
<p><img src="https://img-blog.csdnimg.cn/abef048037f44fe5998d4dfe648a0dd8.png" srcset="/img/loading.gif" lazyload alt="在这里插入图片描述"></p>
<h3 id="7-商品管理页面"><a href="#7-商品管理页面" class="headerlink" title="7. 商品管理页面"></a>7. 商品管理页面</h3><p><img src="https://img-blog.csdnimg.cn/d099af8555bf4eb49a9d27038115e48f.png" srcset="/img/loading.gif" lazyload alt="在这里插入图片描述"></p>
<p><img src="https://img-blog.csdnimg.cn/2b40d8decfeb4cbfa4179f1953f5970b.png" srcset="/img/loading.gif" lazyload alt="在这里插入图片描述"></p>
<p><img src="https://img-blog.csdnimg.cn/5becabd640ba43b4b33735378caca092.png" srcset="/img/loading.gif" lazyload alt="在这里插入图片描述"></p>
<p><img src="https://img-blog.csdnimg.cn/63b8e22ec2c44ea481d7e78c621f02d3.png" srcset="/img/loading.gif" lazyload alt="在这里插入图片描述"></p>
<p><img src="https://img-blog.csdnimg.cn/a6d9d1b17ef847d0be8b526636212437.png" srcset="/img/loading.gif" lazyload alt="在这里插入图片描述"></p>
<h3 id="8-账单管理页面"><a href="#8-账单管理页面" class="headerlink" title="8. 账单管理页面"></a>8. 账单管理页面</h3><p><img src="https://img-blog.csdnimg.cn/777d2959c87a4f9d8d69dca1b31a2239.png" srcset="/img/loading.gif" lazyload alt="在这里插入图片描述"></p>
<p><img src="https://img-blog.csdnimg.cn/42d2283f6f2546ba9b5fa21012c21c36.png" srcset="/img/loading.gif" lazyload alt="在这里插入图片描述"></p>
<p><img src="https://img-blog.csdnimg.cn/2f58bcae36e7495cbf8eab27aa4101f6.png" srcset="/img/loading.gif" lazyload alt="在这里插入图片描述"></p>
<p><img src="https://img-blog.csdnimg.cn/f98a071f20014761bdfb65c19c94b06d.png" srcset="/img/loading.gif" lazyload alt="在这里插入图片描述"></p>
<h3 id="9-角色权限管理页面"><a href="#9-角色权限管理页面" class="headerlink" title="9. 角色权限管理页面"></a>9. 角色权限管理页面</h3><p><img src="https://img-blog.csdnimg.cn/f38aa2aa759d419795965d28cc6b3b89.png" srcset="/img/loading.gif" lazyload alt="在这里插入图片描述"></p>
<p><img src="https://img-blog.csdnimg.cn/538d34ec83e1402f9692de3666caf440.png" srcset="/img/loading.gif" lazyload alt="在这里插入图片描述"></p>
<p><img src="https://img-blog.csdnimg.cn/5f1e255c915c4761b89fdbb40040e570.png" srcset="/img/loading.gif" lazyload alt="在这里插入图片描述"></p>
<h3 id="10-权限列表管理页面"><a href="#10-权限列表管理页面" class="headerlink" title="10. 权限列表管理页面"></a>10. 权限列表管理页面</h3><p><img src="https://img-blog.csdnimg.cn/9ef5d9da2d7e46e992505eefd072d34b.png" srcset="/img/loading.gif" lazyload alt="在这里插入图片描述"></p>
<p><img src="https://img-blog.csdnimg.cn/34ff26e283c14cdfbba71712fc4afd65.png" srcset="/img/loading.gif" lazyload alt="在这里插入图片描述"></p>
<p><img src="https://img-blog.csdnimg.cn/1c3c29837ece46adbde649d9627db75c.png" srcset="/img/loading.gif" lazyload alt="在这里插入图片描述"></p>
<h3 id="11-个人信息页面"><a href="#11-个人信息页面" class="headerlink" title="11. 个人信息页面"></a>11. 个人信息页面</h3><p><img src="https://img-blog.csdnimg.cn/26f155121bdf4d36b252e26f09a12249.png" srcset="/img/loading.gif" lazyload alt="在这里插入图片描述"></p>
<p><img src="https://img-blog.csdnimg.cn/4387cc9c56bd46eaa007880c44f26fb1.png" srcset="/img/loading.gif" lazyload alt="在这里插入图片描述"><br><img src="https://img-blog.csdnimg.cn/f8d3b134a67a414f8542995ef00e6e1a.png" srcset="/img/loading.gif" lazyload alt="在这里插入图片描述"></p>
<p><img src="https://img-blog.csdnimg.cn/0d0b6a3e247a43b38135ae09aa43e9d1.png" srcset="/img/loading.gif" lazyload alt="在这里插入图片描述"></p>
<p><img src="https://img-blog.csdnimg.cn/358d3b5b953146489106cfec7c27a83d.png" srcset="/img/loading.gif" lazyload alt="在这里插入图片描述"></p>
<h1 id="五、代码获取"><a href="#五、代码获取" class="headerlink" title="五、代码获取"></a>五、代码获取</h1><p>这个项目本来就没什么技术含量，所以我决定直接开源，希望能帮助到有需要的朋友吧，也希望自己将来有更好的技术水平来优化一下。</p>
<p>安全起见，我关闭了 OSS的可写功能，所以部署项目后的上传自定义图片功能将失效，过段时间我会考虑将 OSS 移除，采用本地存储，这样就可以一键部署成功了。</p>
<p>项目地址：<a target="_blank" rel="noopener" href="https://gitee.com/ccuni/springboot-mall-system">Gitee  </a></p>

                
              </div>
            
            <hr/>
            <div>
              <div class="post-metas my-3">
  
    <div class="post-meta mr-3 d-flex align-items-center">
      <i class="iconfont icon-category"></i>
      

<span class="category-chains">
  
  
    
      <span class="category-chain">
        
  <a href="/categories/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/" class="category-chain-item">项目实战</a>
  
  

      </span>
    
  
</span>

    </div>
  
  
    <div class="post-meta">
      <i class="iconfont icon-tags"></i>
      
        <a href="/tags/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/">#项目实战</a>
      
    </div>
  
</div>


              
  

  <div class="license-box my-3">
    <div class="license-title">
      <div>项目实战4_ 基于 SpringBoot 的超市账单管理系统</div>
      <div>http://example.com/2022/08/03/项目实战4_ 基于 SpringBoot 的超市账单管理系统/</div>
    </div>
    <div class="license-meta">
      
        <div class="license-meta-item">
          <div>作者</div>
          <div>John Doe</div>
        </div>
      
      
        <div class="license-meta-item license-meta-date">
          <div>发布于</div>
          <div>2022年8月3日</div>
        </div>
      
      
      <div class="license-meta-item">
        <div>许可协议</div>
        <div>
          
            
            
              <a target="_blank" href="https://creativecommons.org/licenses/by/4.0/">
              <span class="hint--top hint--rounded" aria-label="BY - 署名">
                <i class="iconfont icon-by"></i>
              </span>
              </a>
            
          
        </div>
      </div>
    </div>
    <div class="license-icon iconfont"></div>
  </div>



              
                <div class="post-prevnext my-3">
                  <article class="post-prev col-6">
                    
                    
                      <a href="/2022/08/03/%E6%9C%88%E5%BA%A6%E6%80%BB%E7%BB%93%20_%202022%E5%B9%B404%E6%9C%88%20_%20%E5%A4%A7%E6%95%B0%E6%8D%AE%E6%8A%80%E6%9C%AF%E5%AD%A6%E4%B9%A0%E5%BF%83%E5%BE%97%20_%20%E5%A4%A7%E4%B8%89%E4%B8%8B%E6%A0%A1%E5%86%85%E8%AF%BE%E7%A8%8B%E7%9A%84%E5%A4%84%E7%90%86/" title="月度总结 _ 2022年04月 _ 大数据技术学习心得 _ 大三下校内课程的处理">
                        <i class="iconfont icon-arrowleft"></i>
                        <span class="hidden-mobile">月度总结 _ 2022年04月 _ 大数据技术学习心得 _ 大三下校内课程的处理</span>
                        <span class="visible-mobile">上一篇</span>
                      </a>
                    
                  </article>
                  <article class="post-next col-6">
                    
                    
                      <a href="/2022/07/29/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%20%E7%AC%94%E8%AE%B011%20_%20%E5%88%9B%E5%BB%BA%E5%9E%8B%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%9C%A8%E6%BA%90%E7%A0%81%E4%B8%AD%E7%9A%84%E5%BA%94%E7%94%A8%20%E6%80%BB%E7%BB%93%E7%AF%87%EF%BC%88%E4%B8%80%EF%BC%89/" title="设计模式 笔记11 _ 创建型设计模式在源码中的应用 总结篇（一）">
                        <span class="hidden-mobile">设计模式 笔记11 _ 创建型设计模式在源码中的应用 总结篇（一）</span>
                        <span class="visible-mobile">下一篇</span>
                        <i class="iconfont icon-arrowright"></i>
                      </a>
                    
                  </article>
                </div>
              
            </div>

            
          </article>
        </div>
      </div>
    </div>

    <div class="side-col d-none d-lg-block col-lg-2">
      
  <aside class="sidebar" style="margin-left: -1rem">
    <div id="toc">
  <p class="toc-header"><i class="iconfont icon-list"></i>&nbsp;目录</p>
  <div class="toc-body" id="toc-body"></div>
</div>



  </aside>


    </div>
  </div>
</div>





  



  



  



  



  







    

    
      <a id="scroll-top-button" aria-label="TOP" href="#" role="button">
        <i class="iconfont icon-arrowup" aria-hidden="true"></i>
      </a>
    

    
      <div class="modal fade" id="modalSearch" tabindex="-1" role="dialog" aria-labelledby="ModalLabel"
     aria-hidden="true">
  <div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
    <div class="modal-content">
      <div class="modal-header text-center">
        <h4 class="modal-title w-100 font-weight-bold">搜索</h4>
        <button type="button" id="local-search-close" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body mx-3">
        <div class="md-form mb-5">
          <input type="text" id="local-search-input" class="form-control validate">
          <label data-error="x" data-success="v" for="local-search-input">关键词</label>
        </div>
        <div class="list-group" id="local-search-result"></div>
      </div>
    </div>
  </div>
</div>

    

    
  </main>

  <footer>
    <div class="footer-inner">
  
    <div class="footer-content">
       <a href="https://hexo.io" target="_blank" rel="nofollow noopener"><span>Hexo</span></a> <i class="iconfont icon-love"></i> <a href="https://github.com/fluid-dev/hexo-theme-fluid" target="_blank" rel="nofollow noopener"><span>Fluid</span></a> 
    </div>
  
  
  
  
</div>

  </footer>

  <!-- Scripts -->
  
  <script  src="https://lib.baomitu.com/nprogress/0.2.0/nprogress.min.js" ></script>
  <link  rel="stylesheet" href="https://lib.baomitu.com/nprogress/0.2.0/nprogress.min.css" />

  <script>
    NProgress.configure({"showSpinner":false,"trickleSpeed":100})
    NProgress.start()
    window.addEventListener('load', function() {
      NProgress.done();
    })
  </script>


<script  src="https://lib.baomitu.com/jquery/3.6.0/jquery.min.js" ></script>
<script  src="https://lib.baomitu.com/twitter-bootstrap/4.6.1/js/bootstrap.min.js" ></script>
<script  src="/js/events.js" ></script>
<script  src="/js/plugins.js" ></script>


  <script  src="https://lib.baomitu.com/typed.js/2.0.12/typed.min.js" ></script>
  <script>
    (function (window, document) {
      var typing = Fluid.plugins.typing;
      var subtitle = document.getElementById('subtitle');
      if (!subtitle || !typing) {
        return;
      }
      var text = subtitle.getAttribute('data-typed-text');
      
        typing(text);
      
    })(window, document);
  </script>




  
    <script  src="/js/img-lazyload.js" ></script>
  




  
<script>
  Fluid.utils.createScript('https://lib.baomitu.com/tocbot/4.18.2/tocbot.min.js', function() {
    var toc = jQuery('#toc');
    if (toc.length === 0 || !window.tocbot) { return; }
    var boardCtn = jQuery('#board-ctn');
    var boardTop = boardCtn.offset().top;

    window.tocbot.init({
      tocSelector     : '#toc-body',
      contentSelector : '.markdown-body',
      headingSelector : CONFIG.toc.headingSelector || 'h1,h2,h3,h4,h5,h6',
      linkClass       : 'tocbot-link',
      activeLinkClass : 'tocbot-active-link',
      listClass       : 'tocbot-list',
      isCollapsedClass: 'tocbot-is-collapsed',
      collapsibleClass: 'tocbot-is-collapsible',
      collapseDepth   : CONFIG.toc.collapseDepth || 0,
      scrollSmooth    : true,
      headingsOffset  : -boardTop
    });
    if (toc.find('.toc-list-item').length > 0) {
      toc.css('visibility', 'visible');
    }
  });
</script>


  <script src=https://lib.baomitu.com/clipboard.js/2.0.10/clipboard.min.js></script>

  <script>Fluid.plugins.codeWidget();</script>


  
<script>
  Fluid.utils.createScript('https://lib.baomitu.com/anchor-js/4.3.1/anchor.min.js', function() {
    window.anchors.options = {
      placement: CONFIG.anchorjs.placement,
      visible  : CONFIG.anchorjs.visible
    };
    if (CONFIG.anchorjs.icon) {
      window.anchors.options.icon = CONFIG.anchorjs.icon;
    }
    var el = (CONFIG.anchorjs.element || 'h1,h2,h3,h4,h5,h6').split(',');
    var res = [];
    for (var item of el) {
      res.push('.markdown-body > ' + item.trim());
    }
    if (CONFIG.anchorjs.placement === 'left') {
      window.anchors.options.class = 'anchorjs-link-left';
    }
    window.anchors.add(res.join(', '));
  });
</script>


  
<script>
  Fluid.utils.createScript('https://lib.baomitu.com/fancybox/3.5.7/jquery.fancybox.min.js', function() {
    Fluid.plugins.fancyBox();
  });
</script>


  <script>Fluid.plugins.imageCaption();</script>

  <script  src="/js/local-search.js" ></script>





<!-- 主题的启动项，将它保持在最底部 -->
<!-- the boot of the theme, keep it at the bottom -->
<script  src="/js/boot.js" ></script>


  

  <noscript>
    <div class="noscript-warning">博客在允许 JavaScript 运行的环境下浏览效果更佳</div>
  </noscript>
</body>
</html>
